package net.stogi.unicore.beyond.api.model;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import net.stogi.unicore.beyond.api.exception.InputGroupAlreadyExistsException;
import net.stogi.unicore.beyond.api.exception.OutputGroupAlreadyExistsException;

/**
 * Job definition entity.
 * 
 * @author Michał Stogowski
 */
public class TaskDefinition extends Entity {

	private static final long serialVersionUID = 787880842161667299L;
	
	private String applicationDescription;

	private String applicationName;

	private String applicationVersion;

	private List<InputGroup> inputGroups;

	private List<OutputGroup> outputGroups;

	public TaskDefinition() {
		inputGroups = new ArrayList<InputGroup>();
		outputGroups = new ArrayList<OutputGroup>();
	}

	public InputGroup addInputGroup(String name) throws InputGroupAlreadyExistsException {
		assert name != null;
		if (containsInputGroupWithName(name)) {
			throw new InputGroupAlreadyExistsException(name);
		}
		InputGroup inputGroup = new InputGroup(name);
		inputGroup.setIndex(inputGroups.size());
		inputGroup.setTaskDefinition(this);
		inputGroups.add(inputGroup);
		return inputGroup;
	}

	public OutputGroup addOutputGroup(String name) throws OutputGroupAlreadyExistsException {
		assert name != null;
		if (containsOutputGroupWithName(name)) {
			throw new OutputGroupAlreadyExistsException(name);
		}
		OutputGroup outputGroup = new OutputGroup(name);
		outputGroup.setIndex(outputGroups.size());
		outputGroup.setTaskDefinition(this);
		outputGroups.add(outputGroup);
		return outputGroup;
	}

	public boolean containsInputDefinitionWithName(String name) {
		assert name != null;
		for (InputGroup inputGroup : inputGroups) {
			if (inputGroup.containsInputDefinitionWithName(name)) {
				return true;
			}
		}
		return false;
	}

	public boolean containsInputGroupWithName(String name) {
		assert name != null;
		for (InputGroup inputGroup : inputGroups) {
			if (name.equals(inputGroup.getName())) {
				return true;
			}
		}
		return false;
	}

	public boolean containsOutputDefinitionWithName(String name) {
		assert name != null;
		for (OutputGroup outputGroup : outputGroups) {
			if (outputGroup.containsOutputDefinitionWithName(name)) {
				return true;
			}
		}
		return false;
	}

	public boolean containsOutputGroupWithName(String name) {
		assert name != null;
		for (OutputGroup outputGroup : outputGroups) {
			if (name.equals(outputGroup.getName())) {
				return true;
			}
		}
		return false;
	}

	public String getApplicationDescription() {
		return applicationDescription;
	}

	public String getApplicationName() {
		return applicationName;
	}

	public String getApplicationVersion() {
		return applicationVersion;
	}

	public InputGroup getInputGroup(int index) {
		return inputGroups.get(index);
	}

	public List<InputGroup> getInputGroups() {
		return inputGroups;
	}

	public OutputGroup getOutputGroup(int index) {
		return outputGroups.get(index);
	}

	public List<OutputGroup> getOutputGroups() {
		return outputGroups;
	}

	public boolean moveInputGroup(InputGroup inputGroup, int offset) {
		assert inputGroup != null;
		final int index = inputGroup.getIndex();
		final int newIndex = index + offset;
		if (newIndex < 0 || newIndex >= inputGroups.size()) {
			// TODO: Maybe exception ?
			return false;
		}
		inputGroups.remove(index);
		inputGroups.add(newIndex, inputGroup);
		final int start, end;
		if (newIndex < index) {
			start = newIndex;
			end = index;
		}
		else {
			start = index;
			end = newIndex;
		}
		for (int i = start; i <= end; i++) {
			inputGroups.get(i).setIndex(i);
		}
		return true;
	}

	public boolean moveOutputGroup(OutputGroup outputGroup, int offset) {
		assert outputGroup != null;
		final int index = outputGroup.getIndex();
		final int newIndex = index + offset;
		if (newIndex < 0 || newIndex >= outputGroups.size()) {
			// TODO: Maybe exception ?
			return false;
		}
		outputGroups.remove(index);
		outputGroups.add(newIndex, outputGroup);
		final int start, end;
		if (newIndex < index) {
			start = newIndex;
			end = index;
		}
		else {
			start = index;
			end = newIndex;
		}
		for (int i = start; i <= end; i++) {
			outputGroups.get(i).setIndex(i);
		}
		return true;
	}

	public boolean removeInputDefinition(InputDefinition inputDefinition) {
		assert inputDefinition != null;
		boolean removed = false;
		for (InputGroup inputGroup : inputGroups) {
			if (inputGroup.removeInputDefinition(inputDefinition)) {
				removed = true;
			}
		}
		return removed;
	}

	public boolean removeInputGroup(InputGroup inputGroup) {
		assert inputGroup != null;
		boolean removed = false;
		final int removedIndex = inputGroup.getIndex();
		if (inputGroups.remove(inputGroup)) {
			removed = true;
			for (int index = removedIndex; index < inputGroups.size(); index++) {
				inputGroups.get(index).setIndex(index);
			}
		}
		return removed;
	}

	public boolean removeOutputDefinition(OutputDefinition outputDefinition) {
		assert outputDefinition != null;
		boolean removed = false;
		for (OutputGroup outputGroup : outputGroups) {
			if (outputGroup.removeOutputDefinition(outputDefinition)) {
				removed = true;
			}
		}
		return removed;
	}

	public boolean removeOutputGroup(OutputGroup outputGroup) {
		assert outputGroup != null;
		boolean removed = false;
		final int removedIndex = outputGroup.getIndex();
		if (outputGroups.remove(outputGroup)) {
			removed = true;
			for (int index = removedIndex; index < outputGroups.size(); index++) {
				outputGroups.get(index).setIndex(index);
			}
		}
		return removed;
	}

	public InputDefinition selectInputDefinitionWithName(String name) {
		assert name != null;
		for (InputGroup inputGroup : inputGroups) {
			InputDefinition inputDefinition = inputGroup.selectInputDefinitionByName(name);
			if (inputDefinition != null) {
				return inputDefinition;
			}
		}
		return null;
	}

	public InputGroup selectInputGroupWithInputDefinition(InputDefinition inputDefinition) {
		assert inputDefinition != null;
		assert inputDefinition.getName() != null;
		for (InputGroup inputGroup : inputGroups) {
			if (inputGroup.containsInputDefinitionWithName(inputDefinition.getName())) {
				return inputGroup;
			}
		}
		return null;
	}

	public InputGroup selectInputGroupWithName(String name) {
		assert name != null;
		for (InputGroup inputGroup : inputGroups) {
			if (name.equals(inputGroup.getName())) {
				return inputGroup;
			}
		}
		return null;
	}

	public InputGroup selectInputGroupWithId(Long id) {
		if (id == null) {
			throw new IllegalArgumentException("Parameter 'id' can't be null");
		}
		for (InputGroup inputGroup : inputGroups) {
			if (id.equals(inputGroup.getId())) {
				return inputGroup;
			}
		}
		return null;
	}

	public OutputDefinition selectOutputDefinitionWithName(String name) {
		assert name != null;
		for (OutputGroup outputGroup : outputGroups) {
			OutputDefinition outputDefinition = outputGroup.selectOutputDefinitionByName(name);
			if (outputDefinition != null) {
				return outputDefinition;
			}
		}
		return null;
	}

	public OutputGroup selectOutputGroupWithName(String name) {
		assert name != null;
		for (OutputGroup outputGroup : outputGroups) {
			if (name.equals(outputGroup.getName())) {
				return outputGroup;
			}
		}
		return null;
	}

	public OutputGroup selectOutputGroupWithId(Long id) {
		assert id != null;
		for (OutputGroup outputGroup : outputGroups) {
			if (id.equals(outputGroup.getId())) {
				return outputGroup;
			}
		}
		return null;
	}

	public OutputGroup selectOutputGroupWithOutputDefinition(OutputDefinition outputDefinition) {
		assert outputDefinition != null;
		assert outputDefinition.getName() != null;
		for (OutputGroup outputGroup : outputGroups) {
			if (outputGroup.containsOutputDefinitionWithName(outputDefinition.getName())) {
				return outputGroup;
			}
		}
		return null;
	}

	public Set<InputDefinition> selectAllInputDefinitions() {
		Set<InputDefinition> inputDefinitions = new HashSet<InputDefinition>();
		for (InputGroup inputGroup : inputGroups) {
			inputDefinitions.addAll(inputGroup.getInputDefinitions());
		}
		return inputDefinitions;
	}

	public void setApplicationDescription(String applicationDescription) {
		this.applicationDescription = applicationDescription;
	}

	public void setApplicationName(String applicationName) {
		this.applicationName = applicationName;
	}

	public void setApplicationVersion(String applicationVersion) {
		this.applicationVersion = applicationVersion;
	}

	public void setInputGroups(List<InputGroup> inputGroups) {
		this.inputGroups = inputGroups;
	}

	public void setOutputGroups(List<OutputGroup> outputGroups) {
		this.outputGroups = outputGroups;
	}

	@Override
	public String toString() {
		String representation = super.toString();
		
		StringBuilder sb = new StringBuilder("TaskDefinition(" + representation + "): [ ")
				.append("id = '" + id + "', ")
				.append("applicationName = '" + applicationName + "', ")
				.append("applicationVersion = '" + applicationVersion + "', ")
				.append("applicationDescription = '" + applicationDescription + "' ]")
				.append("\n");
		
		for (InputGroup inputGroup : inputGroups) {
			sb.append("\t").append("InputGroup(" + inputGroup.toString() + "): [ ")
					.append("id = '" + inputGroup.getId() + "', ")
					.append("name = '" + inputGroup.getName() + "', ")
					.append("index = '" + inputGroup.getIndex() + "' ]")
					.append("\n");
			
			for (InputDefinition inputDefinition : inputGroup.getInputDefinitions()) {
				sb.append("\t\t").append("InputDefinition(" + inputDefinition.toString() + "): [ ")
						.append("id = '" + inputDefinition.getId() + "', ")
						.append("name = '" + inputDefinition.getName() + "', ")
						.append("index = '" + inputDefinition.getIndex() + "', ")
						.append("inputGroup = '" + inputDefinition.getInputGroup() + "' ]")
						.append("\n");
			}
		}
		
		for (OutputGroup outputGroup : outputGroups) {
			sb.append("\t").append("OutputGroup: [ ")
					.append("id = '" + outputGroup.getId() + "', ")
					.append("name = '" + outputGroup.getName() + "', ")
					.append("index = '" + outputGroup.getIndex() + "' ]")
					.append("\n");
			
			for (OutputDefinition outputDefinition : outputGroup.getOutputDefinitions()) {
				sb.append("\t\t").append("OutputDefinition: [ ")
						.append("id = '" + outputDefinition.getId() + "', ")
						.append("name = '" + outputDefinition.getName() + "', ")
						.append("index = '" + outputDefinition.getIndex() + "', ")
						.append("outputGroup = '" + outputDefinition.getOutputGroup() + "' ]")
						.append("\n");
			}
		}

		return sb.toString();
	}
}
